#ifndef __open_vip_axi4_async__
#define __open_vip_axi4_async__

#include <cstddef>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <queue>
#include <sys/types.h>
#include "xspcomm/xdata.h"
#include "xspcomm/xport.h"
#include "xspcomm/xclock.h"
#include "xspcomm/xutil.h"

namespace ovip {

/* Data Type */

typedef uint64_t AXIAddr;
typedef std::vector<uint8_t> AXIData;
typedef std::vector<AXIData> AXIDataVec;

enum BurstType {
    FIXED = 0,
    INCR = 1,
    WARP = 2
};

enum AXIResp {
    OKAY = 0,
    EXOKAY = 1,
    SLVERR = 2,
    DECERR = 3
};

/* Callback Type */

class AXI4MasterPortAS;
typedef std::function<void(AXI4MasterPortAS *master, AXIData read_data,
                      void *args)>
    ReadCB;

typedef std::function<void(AXI4MasterPortAS *master, AXIDataVec& read_data,
                      void *args)>
    ReadBurstCB;

typedef std::function<void(AXI4MasterPortAS *master, AXIResp write_resp,
                      void *args)>
    WriteCB;

/* AXI4 Task */

enum AXI4TaskType { READ, READ_BURST, WRITE, WRITE_BURST };

class AXI4Task {
public:
    AXI4TaskType type;
    AXIAddr addr;
    int len;
    AXI4Task(AXI4TaskType type, AXIAddr addr, int len = 1):
        type(type), addr(addr), len(len) {}
    virtual void apply(AXI4MasterPortAS *master) = 0;
};

class AXI4TaskRead : public AXI4Task {
public:
    AXI4TaskRead(AXIAddr addr, ReadCB cb) : AXI4Task(READ, addr) {
        this->cb = cb;
    }
    virtual void apply(AXI4MasterPortAS *master);
    ReadCB cb;
};

class AXI4TaskReadBurst : public AXI4Task {
public:
    AXI4TaskReadBurst(AXIAddr addr, int len, ReadBurstCB cb,
                      BurstType burst_type = INCR) : AXI4Task(READ_BURST, addr, len) {
        this->cb = cb;
        this->burst_type = burst_type;
    }
    virtual void apply(AXI4MasterPortAS *master);
    ReadBurstCB cb;
    BurstType burst_type;
};

class AXI4TaskWrite : public AXI4Task {
public:
    AXI4TaskWrite(AXIAddr addr, AXIData data, WriteCB cb) : AXI4Task(WRITE, addr) {
        this->data = data;
        this->cb = cb;
    }
    virtual void apply(AXI4MasterPortAS *master);
    AXIData data;
    WriteCB cb;
};

class AXI4TaskWriteBurst : public AXI4Task {
public:
    AXI4TaskWriteBurst(AXIAddr addr, const AXIDataVec& data, WriteCB cb,
                       BurstType burst_type = INCR) :
                            AXI4Task(WRITE_BURST, addr, data.size()) {
        this->data = data;
        this->cb = cb;
        this->burst_type = burst_type;
    }
    virtual void apply(AXI4MasterPortAS *master);
    AXIDataVec data;
    WriteCB cb;
    BurstType burst_type;
};

/* AXI4 Master Port */

class AXI4MasterPortAS {
public:
    static const uint8_t AxSIZE = 0x3;

public:
    AXI4MasterPortAS(std::string name, xspcomm::XPort &dut_port, xspcomm::XClock &clk,
                   std::string prefix);

    ~AXI4MasterPortAS();

    void reset_status();
    bool is_busy();

    /* READ */
    void read(AXIAddr addr, ReadCB cb);
    void read_burst(AXIAddr addr, int len, ReadBurstCB readburst_cb,
                         BurstType burst_type = INCR);

    /* WRITE */
    void write(AXIAddr addr, AXIData data, WriteCB write_cb);
    void write_burst(AXIAddr addr, const AXIDataVec& data, WriteCB write_cb,
                     BurstType burst_type = INCR);

public:
    std::string mName;
    std::string mPrefix;




// ----------------------------------------------
//  PRIVATE SECTION BELOW
// ----------------------------------------------

private:
    friend class AXI4Task;
    friend class AXI4TaskRead;
    friend class AXI4TaskReadBurst;
    friend class AXI4TaskWrite;
    friend class AXI4TaskWriteBurst;

    xspcomm::XPort *all_ports;
    xspcomm::XPort *ar_port, *r_port, *aw_port, *w_port, *b_port;
    xspcomm::XClock *clk;

    std::unique_ptr<AXI4Task> read_task_in_progress = nullptr;
    std::unique_ptr<AXI4Task> write_task_in_progress = nullptr;
    std::queue<std::unique_ptr<AXI4Task>> task_queue;

    // Channel R
    AXIDataVec data_read;

    // Channel AW
    bool aw_busy;

    // Channel W
    std::queue<AXIData> data_to_write;
    bool w_busy;
};

}

#endif
